package com.conph.moring.core;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;

import com.conph.moring.core.annotation.Autowired;
import com.conph.moring.core.annotation.Bean;
import com.conph.moring.core.annotation.Component;
import com.mec.util.PackageScanner;
import com.sun.xml.internal.bind.v2.runtime.unmarshaller.Intercepter;

public class BeanFactory {
	/* 现在搞明白了Spring注解的含义了
	 * 注解类型都有：‘@Component’、‘@Bean’、‘@Autowired’
	 * 现在阐述这里面的关系：
	 * ‘@Component’注解是给一些类加的，是为了在使用这些类的对象的时候不再去亲自new，直接可以从beanFactory里取到。
	 * 并且可以加上‘@Bean’注解和‘@Autowired注解的辅助’，可以使这些类的对象满足意愿具有一定的内容而不至于为空。
	 * 而这里面存在一个问题，A类的Autowired成员如果是B类类型的，
	 * 而这个B类（自然加了@Component注解）类型又有一个C类（自然加了@Component注解）类型的Autowired成员，
	 * 这个C类类型的成员又有一个A类类型的成员，这样就没没有办法再继续注入了（或者这些成员只能被注入为空），如果不加以控制，就会产生
	 * 搜所谓的‘循环注入依赖’导致注入死循环。解决方法是给这个类的对象在注入前就加上一个已经注入的标记，这个标记默认在对象产生的时候为False，
	 * 之后再进行递归注入，将此对象的标记值作为递归是否进行下去的控制条件就可以解决。
	 *
	 * 再有一个问题，就是给方法加Bean注解的意义和作用。现在站出来作为框架使用者的考虑，现在会存在这样一个问题：
	 * 假设某个类的某个成员的类型是一个Jar包内的类的类，直白说就是没有这个类的‘.class’文件，也就是没有这个类的源代码，无法通过加@Component
	 * 注解，经过之后的包扫描再去加入到bean工厂的方式去产生这个对象，所以需要通过Bean注解来实现。
	 * 方法是给拥有这个成员的类加一个get的方法（无参），让他返回这个类的对象。在处理bean注解时执行
	 * 这个方法，并将这个返回值存入bean工厂中，为以后的注入操作做准备。
	 *
	 * bean注解的处理时机与处理过程：
	 *
	 * 在扫描到每个Component注解类之后，扫描加了bean注解的方法，准备执行这些方法。
	 * 方法当然是分无参和非无参的。如果带参的方法中有参数的类型是没有源代码的（也就是上文说的情况），那就可以配合加上一个具有bean注解的get方法get到这个
	 * 类类型的对象，这样在bean注解方法中先会处理这个无参的get方法，之后再将返回的对象也就是别的bean方法会用到的对象参数加入到bean工厂中去，
	 * 在带参方法调用时准备的参数从bean工厂中取得。
	 *
	 *
	 *
	 *
	 * 作用：
	 * 提供给外部使用，扫描用户所传递的包名下的类是否含有事先写好的‘@Component’、‘Autowired’、‘@Bean’注解，
	 * (Autowired目前先不解决)
	 * 并将类的对象按照所加注解的要求进行注入，将注入好的对象提供给外部得到...
	 * 目前会出现这样的问题：如果用AOP产生了一个类A的对象，要注入这个类的对象（包括给此对象注入这个类中加了@Bean的方法成员、
	 * 用该对象作为前缀调用这些方法），此时发现类中的成员有B类的对象也需要注入，B类的对象也是加了）
	 *	当时写的关于循环注入的问题，已经在博文中说明并解决。
	 * */
	private static ArrayList<BeanMethodDefination> methodList = new ArrayList<>();

	/**
	 * 无参构造可选。用户可以直接使用类名调用静态方法对Component包与Aspect包进行扫描。
	 * 或者，通过无参构造对此类进行初始化。初始化过程中的包名将默认从配置文件中读取。
	 */
	public BeanFactory() {
	}

	public BeanFactory(String componentPath, String aspectPath) {
		scanPackage(componentPath);
		IntercepterScanner.intercepterScanner(aspectPath);
	}

	public static void scanPackage(String packageName) {
		ProxyBeanFactory proxyBeanFactory = new ProxyBeanFactory();
		//对指定包路径下的类进行扫描
		/**
		 * 层级关系依次为：@Component -> @Bean
		 */
		new PackageScanner() {
			@Override
			public void dealClass(Class<?> klass) {
				if(!klass.isAnnotationPresent(Component.class)) {
					return;
				}
				String beanName = klass.getAnnotation(Component.class).name();
				String className = klass.getName();
				try {
					proxyBeanFactory.getCGLProxy(klass);
					Object object = proxyBeanFactory.getMecProxy(klass).getObject();
					
					Method[] methods = klass.getDeclaredMethods();
					for(Method method : methods) {
						if(!method.isAnnotationPresent(Bean.class)) {
							continue;
						}
						invokeBeanMethod(method, klass, object, proxyBeanFactory, beanName);
					}
					//如果有别名，则进行别名添加
					if (beanName.length() > 0) {
						proxyBeanFactory.addBean(beanName, className);
					}
				} catch (Exception e) {
					e.printStackTrace();
				}

			}
		}.packageScan(packageName);
		//执行带参的Bean注解的方法
		for(BeanMethodDefination beanMethodDefination : methodList) {
			Object object = beanMethodDefination.getObject();
			Class<?> klass = beanMethodDefination.getClass();
			Method method = beanMethodDefination.getMethod();
			Class<?>[] parametersClass = beanMethodDefination.getParameters();
			String beanName = beanMethodDefination.getBeanName();
			
			try {
				invokeMulBeanMethod(method, klass, object, parametersClass, proxyBeanFactory, beanName);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		methodList.clear();
	}

	/**
	 *
	 * 	先执行无参的方法，添加其返回值到Bean工厂中。避免出现还未扫描到的参数参数
	 * 	然后再将带参的方法存储起来，到最后一并执行
	 * @param method @Bean注解的方法
	 * @param klass @Component注解的类
	 * @param object 注解类的实例对象
	 * @param proxyBeanFactory 代理工厂
	 * @param beanName 别名
	 * @throws Exception
	 */
	private static void invokeBeanMethod(Method method, Class<?> klass, Object object,
			ProxyBeanFactory proxyBeanFactory, String beanName) throws Exception {
		//此处接手所有的method
		
		Class<?>[] parameters = method.getParameterTypes();
		Class<?> returnType = method.getReturnType();
		if(returnType.equals(void.class)) {
			return;
		}

		if(parameters.length <= 0) {
			Object result = method.invoke(object, (Object[]) null);
			createBean(proxyBeanFactory, returnType, result, beanName);
		} else {
			BeanMethodDefination beanMethodDefination = 
					new BeanMethodDefination(method, returnType, parameters, object, beanName);
			methodList.add(beanMethodDefination);
		}
	}
	
	private static void invokeMulBeanMethod(Method method, Class<?> klass, Object object,
			Class<?>[] parametersClass, ProxyBeanFactory proxyBeanFactory, String beanName)
					throws Exception {
		
		Object[] paraObj = new Object[parametersClass.length];
		for(int index = 0; index < parametersClass.length; index++) {
			Object result = proxyBeanFactory.getBeanProxy(parametersClass[index]);
			paraObj[index] = result;
		}
		Object beanObject = method.invoke(object, paraObj);
		createBean(proxyBeanFactory, klass, beanObject, beanName);
		
	}
	
	private static void createBean(ProxyBeanFactory proxyBeanFactory, Class<?> klass, Object object, String beanName)
			throws Exception {
		if(object != null) {
			proxyBeanFactory.getCGLProxy(object);
		} else {
			proxyBeanFactory.getCGLProxy(klass);
		}
		
		if(beanName.length() > 0) {
			proxyBeanFactory.addBean(beanName, klass.getName());
		}
	}
	
	public static <T> T getBean(String beanName) throws Exception {
		ProxyBeanFactory proxyBeanFactory = new ProxyBeanFactory();
		String className = proxyBeanFactory.getBeanClassName(beanName);
		if(className == null) {
			throw new BeanNameNotExistException("【" + beanName + "】" + "不存在!");
		} 
		
		Class<?> klass = Class.forName(className);
		return getBean(klass);
	}

	/**
	 * 在得到相关最终对象的方法中去注入。
	 * 利用MecProxy中的Injection成员作为判断是否注入过的依据，
	 * 选择是否进行注入。
	 * 注入的方式：递归注入
	 * @param proxyBeanFactory 代理工厂
	 * @param object 注入起点类的原对象
	 * @param klass 注入起点类
	 */

	//递归注入
	private static void injectBean (ProxyBeanFactory proxyBeanFactory, Object object, Class<?> klass) {
		MecProxy orgMecProxy = proxyBeanFactory.getMecProxy(klass);
		orgMecProxy.setInjection(true);//立即标记本次注入
		
		Field[] fields = klass.getDeclaredFields();
		for(Field field : fields) {
			if(!field.isAnnotationPresent(Autowired.class)) {
				continue;
			}
			Class<?> fieldClass = field.getType();
			MecProxy mecProxy = proxyBeanFactory.getMecProxy(fieldClass);
			if(!mecProxy.isInjection()) {//如果本次注入对象中的Autowired字段没有被注入过，先递归此字段，再注入
				injectBean(proxyBeanFactory, mecProxy.getObject(), fieldClass);
			}
			field.setAccessible(true);
			try {
				//需要注意：此处注入的是成员的代理对象！
				field.set(object, mecProxy.getProxy());
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 在用户想要从Bean工厂获取对象时，
	 * 如果其MecProxy的对象的注入标志显示为未注入，则先进行递归注入
	 * 反之返回其代理对象。
	 * @param klass 你想要得到的对象的类
	 * @return 泛型类型的返回值
	 */
	public static <T> T getBean(Class<?> klass) {
		ProxyBeanFactory proxyBeanFactory = new ProxyBeanFactory();
		MecProxy mecProxy = proxyBeanFactory.getMecProxy(klass);
		if (!mecProxy.isInjection()) {
			injectBean(proxyBeanFactory,mecProxy.getObject() , klass);
		}
		T proxyBean = mecProxy.getProxy();
		
		return proxyBean;
	}
}
